/*
Thunk 函数是自动执行 Generator 函数的一种方法。

参数的求值策略

Thunk 函数早在上个世纪 60 年代就诞生了。

那时，编程语言刚刚起步，计算机学家还在研究，编译器怎么写比较好。一个争论的焦点是"求值策略"，即函数的参数到底应该何时求值。
*/
var x = 1;

function f(m) {
	return m * 2;
}

f(x + 5)
/*
上面代码先定义函数f，然后向它传入表达式x + 5。请问，这个表达式应该何时求值？

一种意见是"传值调用"（call by value），即在进入函数体之前，就计算x + 5的值（等于 6），再将这个值传入函数f。
C 语言就采用这种策略。
*/



/*
另一种意见是“传名调用”（call by name），即直接将表达式x + 5传入函数体，只在用到它的时候求值。
Haskell 语言采用这种策略。
*/
f(x + 5)
// 传名调用时，等同于
(x + 5) * 2
/*
传值调用和传名调用，哪一种比较好？

回答是各有利弊。传值调用比较简单，但是对参数求值的时候，实际上还没用到这个参数，有可能造成性能损失。
*/
function f(a, b) {
	return b;
}

f(3 * x * x - 2 * x - 1, x);
/*
上面代码中，函数f的第一个参数是一个复杂的表达式，但是函数体内根本没用到。
对这个参数求值，实际上是不必要的。因此，有一些计算机学家倾向于"传名调用"，即只在执行时求值。


Thunk 函数的含义

编译器的“传名调用”实现，往往是将参数放到一个临时函数之中，再将这个临时函数传入函数体。
这个临时函数就叫做 Thunk 函数。
*/

function f(m) {
	return m * 2;
}

f(x + 5);

// 等同于

var thunk = function() {
	return x + 5;
};

function f(thunk) {
	return thunk() * 2;
}
/*
上面代码中，函数 f 的参数x + 5被一个函数替换了。凡是用到原参数的地方，对Thunk函数求值即可。

这就是 Thunk 函数的定义，它是“传名调用”的一种实现策略，用来替换某个表达式。


JavaScript 语言的 Thunk 函数

JavaScript 语言是传值调用，它的 Thunk 函数含义有所不同。
在 JavaScript 语言中，Thunk 函数替换的不是表达式，而是多参数函数，将其替换成一个只接受回调函数作为参数的单参数函数。
*/



//任何函数， 只要参数有回调函数， 就能写成 Thunk 函数的形式。 下面是一个简单的 Thunk 函数转换器。
// ES5版本
var Thunk = function(fn) {
	return function() {
		var args = Array.prototype.slice.call(arguments);
		return function(callback) {
			args.push(callback);
			return fn.apply(this, args);
		}
	};
};


// ES6版本
const Thunk = function(fn) {
	return function(...args) {
		return function(callback) {
			return fn.call(this, ...args, callback);
		}
	};
};

//使用上面的转换器， 生成fs.readFile的 Thunk 函数。

var readFileThunk = Thunk(fs.readFile);
readFileThunk(fileA)(callback);

//下面是另一个完整的例子。

function f(a, cb) {
	cb(a);
}
const ft = Thunk(f);

ft(1)(console.log) // 1






/*
Thunkify 模块

生产环境的转换器， 建议使用 Thunkify 模块。

首先是安装。

$ npm install thunkify

使用方式如下。
*/

var thunkify = require('thunkify');
var fs = require('fs');

var read = thunkify(fs.readFile);
read('package.json')(function(err, str) {
	// ...
});

//Thunkify 的源码与上一节那个简单的转换器非常像。

function thunkify(fn) {
	return function() {
		var args = new Array(arguments.length); //参数多少 长度就多少 
		var ctx = this; //将上下文传递

		for (var i = 0; i < args.length; ++i) { //拷贝参数
			args[i] = arguments[i];
		}

		return function(done) {
			var called;

			args.push(function() { //为fs.readFile添加回调函数（因为最后一个参数是回调函数）
				if (called) return; //确保回调函数只运行一次
				called = true;
				done.apply(null, arguments); //arguments是fs.readFile给的
			});

			try {
				fn.apply(ctx, args);
			} catch (err) {
				done(err); //这也解释了为什么err位于首位
			}
		}
	}
};

/*
它的源码主要多了一个检查机制，变量called确保回调函数只运行一次。
这样的设计与下文的 Generator 函数相关。请看下面的例子。
*/

function f(a, b, callback) { //  参数对应1,2,print => 这就是thunkify流，根据核心函数f编写代码
	var sum = a + b;
	callback(sum);
	callback(sum);
}

var ft = thunkify(f);
var print = console.log.bind(console);
ft(1, 2)(print);
// 3 由于thunkify只允许回调函数执行一次，所以只输出一行结果。



/*
Generator 函数的流程管理

你可能会问， Thunk 函数有什么用？回答是以前确实没什么用，但是 ES6 有了 Generator 函数，
Thunk 函数现在可以用于 Generator 函数的自动流程管理。

Generator 函数可以自动执行。
*/

function* gen() {
	// ...
}

var g = gen();
var res = g.next();

while (!res.done) {
	console.log(res.value);
	res = g.next();
}

/*
上面代码中，Generator 函数gen会自动执行完所有步骤。

但是，这不适合异步操作。如果必须保证前一步执行完，才能执行后一步，上面的自动执行就不可行。
这时，Thunk 函数就能派上用处。以读取文件为例。下面的 Generator 函数封装了两个异步操作。
*/
var fs = require('fs');
var thunkify = require('thunkify'); //使异步更灵活
var readFileThunk = thunkify(fs.readFile);

var gen = function*() {
	var r1 = yield readFileThunk('/etc/fstab');
	console.log(r1.toString());
	var r2 = yield readFileThunk('/etc/shells');
	console.log(r2.toString());
};
/*
上面代码中，yield命令用于将程序的执行权移出 Generator 函数，那么就需要一种方法，将执行权再交还给 Generator 函数。

这种方法就是 Thunk 函数，因为它可以在回调函数里，将执行权交还给 Generator 函数。
为了便于理解，我们先看如何手动执行上面这个 Generator 函数。
*/
var g = gen();

var r1 = g.next(); //r1里的value就是个方法 来自fs.readFile的回调函数
r1.value(function(err, data) {
	if (err) throw err; //外部抛出错误  没有出现错误就继续往下执行
	var r2 = g.next(data); //此时输出第一句话
	r2.value(function(err, data) {
		if (err) throw err;
		g.next(data); //此时输出第二句话 
	});
});
/*
上面代码中，变量g是 Generator 函数的内部指针，表示目前执行到哪一步。
next方法负责将指针移动到下一步，并返回该步的信息（value属性和done属性）。

仔细查看上面的代码，可以发现 Generator 函数的执行过程，其实是将同一个回调函数，反复传入next方法的value属性。
这使得我们可以用递归来自动完成这个过程。


Thunk 函数真正的威力，在于可以自动执行 Generator 函数。下面就是一个基于 Thunk 函数的 Generator 执行器。
*/
function run(fn) {
	var gen = fn();

	function next(err, data) {
		var result = gen.next(data);
		if (result.done) return;
		result.value(next); //递归
	}

	next();
}

function* g() {
	// ...
}

run(g);
/*
上面代码的run函数，就是一个 Generator 函数的自动执行器。
内部的next函数就是 Thunk 的回调函数。
next函数先将指针移到 Generator 函数的下一步（gen.next方法），
然后判断 Generator 函数是否结束（result.done属性），如果没结束，就
将next函数再传入 Thunk 函数（result.value属性），否则就直接退出。


有了这个执行器，执行 Generator 函数方便多了。不管内部有多少个异步操作，
直接把 Generator 函数传入run函数即可。当然，前提是每一个异步操作，都要是 Thunk 函数，
也就是说，跟在yield命令后面的必须是 Thunk 函数。
*/
var g = function*() {
	var f1 = yield readFileThunk('fileA');
	var f2 = yield readFileThunk('fileB');
	// ...
	var fn = yield readFileThunk('fileN');
};

run(g);
/*
上面代码中，函数g封装了n个异步的读取文件操作，只要执行run函数，这些操作就会自动完成。
这样一来，异步操作不仅可以写得像同步操作，而且一行代码就可以执行。

Thunk 函数并不是 Generator 函数自动执行的唯一方案。
因为自动执行的关键是，必须有一种机制，自动控制 Generator 函数的流程，接收和交还程序的执行权。
回调函数可以做到这一点，Promise 对象也可以做到这一点。
*/
